using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Xml;

namespace SuDokuSolution
{
	/// <summary>
	/// The Main grid holding all cells
	/// </summary>
	public class GridControl : Control
	{
		
		/// <summary>
		/// Bitmap buffer to avoid flickering
		/// </summary>
		internal Bitmap bufferBitmap;

		/// <summary>
		/// Buffer Graphics to avoid flickering
		/// </summary>
		private Graphics bufferGraphics; 

		/// <summary>
		/// array of Graphical cell
		/// </summary>
		internal GraphicalCell [,] cellGrid;
		
		/// <summary>
		/// Size of cell
		/// </summary>
		private Size cellSize;

		/// <summary>
		/// Size of subcell
		/// </summary>
		private Size subCellSize;

		/// <summary>
		/// Starting location of the cell grid
		/// </summary>
		private Point offset;

		/// <summary>
		/// Whether to redraw or not the entire control
		/// </summary>
		private bool redrawBool=true;

		/// <summary>
		/// Has the Game started
		/// </summary>
		private bool isGameStarted=false;

		/// <summary>
		/// Flag to set auto updation of candidate nbs true
		/// </summary>
		internal bool autoUpdate=false;

		/// <summary>
		/// Flag to set simultaneous checking true
		/// </summary>
		internal bool simultaneousCheck=false;

		/// <summary>
		/// Raised when  a new gam starts
		/// </summary>
		public EventHandler StartGame;

		/// <summary>
		/// Raised when a Game Ends
		/// </summary>
		public EventHandler EndGame;

		/// <summary>
		/// Counter to keep track of filled cells
		/// </summary>
		private int currentlyFilledCells=0;

		/// <summary>
		/// whether generating a puzzle manually or not
		/// </summary>
		internal bool generatingPuzzle=false;

		/// <summary>
		/// Constructor for the grid control
		/// </summary>
		public GridControl()
		{  
			
		}

		/// <summary>
		/// Raises Start Game Event
		/// </summary>
		/// <param name="e">Eventargs e</param>
		public void OnStartGame(EventArgs e)
		{
			if(StartGame!=null)
				StartGame(this,e);
		}
        
		/// <summary>
		/// Raises End Game Event
		/// </summary>
		/// <param name="e">Eventargs e</param>
		public void OnEndGame(EventArgs e)
		{
			if(EndGame!=null)
				EndGame(this,e);
		}

		/// <summary>
		/// Initiallize all the data of the class
		/// </summary>
		public void InitiallizeGrid()
		{
			cellGrid=new GraphicalCell[Constants.nbOfCells,Constants.nbOfCells];
			Brush selBrush=DrawingTools.WhiteBrush;
			int HB,VB;
			for(int row=0;row<Constants.nbOfCells;row++)
			{
				HB=row/Constants.nbOfLines;
				for(int col=0;col<Constants.nbOfCells;col++)
				{					
					VB=col/Constants.nbOfLines;
					if((HB+VB)%2!=0)
						selBrush=DrawingTools.BackGroundBrush1;
					else
						selBrush=DrawingTools.BackGroundBrush2;
					cellGrid[row,col]=new GraphicalCell(this,selBrush);
				}
			}

			computeLocations();
		}

		/// <summary>
		/// To compute location,size etc of grid or cells
		/// </summary>
		public void computeLocations()
		{
			if(cellGrid==null)
				return;
			
			cellSize=new Size(((Size.Width/Constants.nbOfCells)/Constants.nbOfLines)*Constants.nbOfLines-10,
                ((Size.Height/Constants.nbOfCells)/Constants.nbOfLines)*Constants.nbOfLines-8);

            subCellSize=new Size(cellSize.Width/Constants.nbOfLines,cellSize.Height/Constants	.nbOfLines);
            
			//Fonts
            DrawingTools.SelectedFont = new Font("Baskerville Old Face", cellSize.Height / 2, FontStyle.Bold);
            DrawingTools.miniSelectedFont = new Font("Baskerville Old Face", subCellSize.Height / 2, FontStyle.Regular); 

			offset=new Point();
			offset.X=(Size.Width-cellSize.Width*Constants.nbOfCells)/2;
			offset.Y=(Size.Height-cellSize.Height*Constants.nbOfCells)/2;

			for(int row=0;row<Constants.nbOfCells;row++)
			{
				for(int col=0;col<Constants.nbOfCells;col++)
				{
					cellGrid[row,col].cellSize=cellSize;
					cellGrid[row,col].subCellSize=subCellSize;
					cellGrid[row,col].cellLocation=new Point(offset.X+col*cellSize.Width,
						offset.Y+row*cellSize.Height);
					cellGrid[row,col].cellRect=new Rectangle(cellGrid[row,col].cellLocation.X+1,
						cellGrid[row,col].cellLocation.Y+1,cellSize.Width-2,cellSize.Height-2);
				}
			}
            redrawBool=true;
			Invalidate();
		}


		/// <summary>
		/// Method called when control is resized
		/// </summary>
		/// <param name="e"> Event Args</param>
		protected override void OnResize(EventArgs e)
		{
			if(bufferBitmap==null || bufferBitmap.Size!=Size)
			{
				if(Size.Width!=0 && Size.Height!=0)//if not minimized
				{
                    bufferBitmap=new Bitmap(Size.Width,Size.Height);
					bufferGraphics=Graphics.FromImage(bufferBitmap);
					computeLocations();
				}
			}

            base.OnResize(e);
		}


		/// <summary>
		/// called when Control has to be painted
		/// </summary>
		/// <param name="e">Paint Event Args</param>
		protected override void OnPaint(PaintEventArgs e)
		{
			if(redrawBool)
			{
				Pen selPen=DrawingTools.MediumPen;
				bufferGraphics.FillRectangle(DrawingTools.WhiteBrush,e.ClipRectangle);
				for(int i=0;i<=Constants.nbOfCells;i++)
				{
					if(i%Constants.nbOfLines==0)
						selPen=DrawingTools.DarkPen;
					else
						selPen=DrawingTools.MediumPen;
					//horizontal line
					bufferGraphics.DrawLine(selPen,offset.X,offset.Y+cellSize.Height*i,
						offset.X+cellSize.Width*Constants.nbOfCells,offset.Y+cellSize.Height*i);
					
					//vertical line
					bufferGraphics.DrawLine(selPen,offset.X+cellSize.Width*i,offset.Y,
						offset.X+cellSize.Width*i,offset.Y+cellSize.Height*Constants.nbOfCells); 					
				}

				for(int row=0;row<Constants.nbOfCells;row++)
				{
					for(int col=0;col<Constants.nbOfCells;col++)
					{
						cellGrid[row,col].Paint(bufferGraphics);
					}
				}
			}	

			e.Graphics.DrawImage(bufferBitmap,0,0);
			redrawBool=false;
		}
		
		/// <summary>
		/// OnPaintBackground - overrides PaintBackground to avoid flickering
		/// </summary>
		/// <param name="e">paint event args</param>
		protected override void OnPaintBackground(PaintEventArgs e)
		{}

		/// <summary>
		/// force cell repaint 
		/// </summary>
		/// <param name="rcell">the cell to be redrawn</param>
		public void RedrawCell(GraphicalCell rcell)
		{
			rcell.Paint(bufferGraphics);
			Invalidate();
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
            if(!Enabled)
				return;
			
			if(e.X<offset.X || e.Y<offset.Y || 
				e.X>(offset.X+cellSize.Width*Constants.nbOfCells)||
				e.Y>(offset.Y+cellSize.Height*Constants.nbOfCells))
				return;
			
			int row,col;
			col=(e.X-offset.X)/cellSize.Width;
			row=(e.Y-offset.Y)/cellSize.Height;
			if(col>=Constants.nbOfCells || row >=Constants.nbOfCells)
				return;                    

			#region Code for processing clicks while playing game
			if(!generatingPuzzle)
			{
				if(!isGameStarted)
					ExecuteStartGame();
            
				bool changed=false;
				if(e.Clicks==1)
				{
					cellGrid[row,col].MouseDown(e);
				}
				else if(e.Clicks==2)
				{
					cellGrid[row,col].DoubleClick(e);
					changed=true;
				}

				if(changed)
				{
					//if an invalid nb has been selected mark it as error
					if(cellGrid[row,col].cellData.problemNb!=Constants.NoNumber 
						&& !cellGrid[row,col].cellData.candidateNb.Contains(cellGrid[row,col].cellData.problemNb))
					{
						cellGrid[row,col].MarkError=true;      //if an invalid nb has been clicked
					}
					else
					{
						cellGrid[row,col].MarkError=false;
						autoUpdateMarkedCandidates(row,col);					
					}				
					this.redrawBool=true;
					currentlyFilledCells+=(cellGrid[row,col].cellData.problemNb==Constants.NoNumber )?(-1):1;
					if(currentlyFilledCells==Constants.nbOfCells*Constants.nbOfCells)
						CheckEndGame();
				}
				base.OnMouseDown(e);
				Invalidate();
			}
				#endregion

			#region code for processing clicks while generating a problem manually
			else
			{
				GraphicalCell gr=cellGrid[row,col];
				int clickedNb=gr.getClickedNb(e.X-gr.cellLocation.X,e.Y-gr.cellLocation.Y);
			
				if(gr.cellData.problemNb==Constants.NoNumber)
				{
					gr.cellData.problemNb=clickedNb;
					gr.cellData.readOnly=true;
				}
				else
					gr.cellData.problemNb=Constants.NoNumber;
                RedrawCell(gr);
			}
			#endregion
		}
		

	
		/// <summary>
		///autometically update all candidate nbs when a valid nb is selected in a cell
		///  The Validity check should be performed by the calling function
		/// </summary>
		/// <param name="row"></param>
		/// <param name="col"></param>
		private void autoUpdateMarkedCandidates(int row,int col)
		{
			if(autoUpdate)
			{			
				for(int i=0;i<9;i++)
				{
					searchCandidates(row,i);
					cellGrid[row,i].CheckedNbs.Clear();				
					foreach(object ob in cellGrid[row,i].cellData.candidateNb)
						cellGrid[row,i].CheckedNbs.Add(ob);
					if(row!=i)
					{
						searchCandidates(i,col);
						cellGrid[i,col].CheckedNbs.Clear();				
						foreach(object ob in cellGrid[i,col].cellData.candidateNb)
							cellGrid[i,col].CheckedNbs.Add(ob);				
					}
				}
			
				int HB=(int)(row/Constants.nbOfLines);
				int VB=(int) (col/Constants.nbOfLines);            
                          
				for(int i=HB*Constants.nbOfLines;i<(HB+1)*Constants.nbOfLines;i++)
				{
					for(int j=VB*Constants.nbOfLines;j<(VB+1)*Constants.nbOfLines;j++)
					{
						if(i!=row && j!=col )
						{
							//MessageBox.Show("Row ="+i+",col="+j);
							searchCandidates(i,j);
							cellGrid[i,j].CheckedNbs.Clear();				
							foreach(object ob in cellGrid[i,j].cellData.candidateNb)
								cellGrid[i,j].CheckedNbs.Add(ob);				
						}
					}
				}
			}

			else
			{
				for(int i=0;i<9;i++)
				{
					searchCandidates(row,i);
					if(row!=i)
					{
						searchCandidates(i,col);
					}
				}
			
				int HB=(int)(row/Constants.nbOfLines);
				int VB=(int) (col/Constants.nbOfLines);            
                          
				for(int i=HB*Constants.nbOfLines;i<(HB+1)*Constants.nbOfLines;i++)
				{
					for(int j=VB*Constants.nbOfLines;j<(VB+1)*Constants.nbOfLines;j++)
					{
						if(i!=row && j!=col )
						{
							searchCandidates(i,j);
						}
					}
				}
			}

		}

		/// <summary>
		/// searches candidate nbs for a cell given by row,col 
		/// </summary>
		/// <param name="row">row</param>
		/// <param name="col">column</param>
		public void searchCandidates(int row,int col)
		{
			if(cellGrid[row,col].cellData.problemNb!=Constants.NoNumber)
			{
                cellGrid[row,col].cellData.candidateNb.Clear();
				return;
			}
			
			int HB=(int)(row/Constants.nbOfLines);
			int VB=(int) (col/Constants.nbOfLines);
            
			//clear the existing list
			cellGrid[row,col].cellData.candidateNb.Clear();
            
			
			foreach (int number in new int[9]{1,2,3,4,5,6,7,8,9})
			{
				cellGrid[row,col].cellData.candidateNb.Add(number);
			}                   
            
			for(int i=0;i<9;i++)
			{
				cellGrid[row,col].cellData.candidateNb.Remove(cellGrid[row,i].cellData.problemNb);
				cellGrid[row,col].cellData.candidateNb.Remove(cellGrid[i,col].cellData.problemNb);
			}
                          
			for(int i=HB*Constants.nbOfLines;i<(HB+1)*Constants.nbOfLines;i++)
			{
				for(int j=VB*Constants.nbOfLines;j<(VB+1)*Constants.nbOfLines;j++)
				{
					cellGrid[row,col].cellData.candidateNb.Remove(cellGrid[i,j].cellData.problemNb);                                    
				}
			}			
		}
		
		/// <summary>
		/// Raises all the events related to a game start
		/// </summary>
		private void ExecuteStartGame()
		{
			isGameStarted=true;
			OnStartGame(EventArgs.Empty);		
		}

		/// <summary>
		/// Check if Game has ended
		/// </summary>
		private void CheckEndGame()
		{
			int row,col;
			if(varify(out row,out col))
				OnEndGame(EventArgs.Empty);			
		}

		/// <summary>
		/// Returns true if the current grid follows all rules of su doku else the
		/// Index of wrong entry in the wRow,wCol
		/// </summary>
		/// <param name="wRow"></param>
		/// <param name="wCol"></param>
		/// <returns></returns>
		internal bool varify(out int wRow,out int wCol)
		{
			for(int row=0;row<Constants.nbOfCells;row++)
			{
				for(int col=0;col<Constants.nbOfCells;col++)
				{
					int HB=(int)(row/Constants.nbOfLines);
					int VB=(int) (col/Constants.nbOfLines);
					int nb=cellGrid[row,col].cellData.problemNb;
					if(nb==Constants.NoNumber)
						continue;
					for(int i=0;i<Constants.nbOfCells;i++)
					{
						if((cellGrid[row,i].cellData.problemNb==nb && i!=col) || (cellGrid[i,col].cellData.problemNb ==nb && i!=row))
						{
							wRow=row+1;
							wCol=col+1;
							return false;
						}
					}
                          
					for(int i=HB*3;i<(HB+1)*3;i++)
					{
						for(int j=VB*3;j<(VB+1)*3;j++)
						{
							if(cellGrid[i,j].cellData.problemNb==nb && i!=row && i!=col)
							{
								wRow=row+1;
								wCol=col+1;
								return false;
							}                                    
						}
					}      
				}
			}

			wRow=wCol=Constants.NoNumber;
			return true;
		}

		/// <summary>
		/// Load Data in the Grid 
		/// </summary>
		/// <param name="completeGrid">The complete Grid</param>
		/// <param name="problemGrid">The problem Grid</param>
		public void LoadData(int [,]completeGrid,int [,]problemGrid)
		{
			this.currentlyFilledCells=0;
			for(int row=0;row<Constants.nbOfCells;row++)
			{
				for(int col=0;col<Constants.nbOfCells;col++)
				{
					this.cellGrid[row,col].cellData.originalNb=completeGrid[row,col];
					this.cellGrid[row,col].cellData.problemNb=problemGrid[row,col];
					if(problemGrid[row,col]==Constants.NoNumber)
						this.cellGrid[row,col].cellData.readOnly=false;
					else 
					{
						this.cellGrid[row,col].cellData.readOnly=true;
						this.currentlyFilledCells++;
					}
					this.cellGrid[row,col].CheckedNbs.Clear();
				}
			}
			
			for(int row=0;row<Constants.nbOfCells;row++)
			 {
				 for(int col=0;col<Constants.nbOfCells;col++)
				 {
					 searchCandidates(row,col);
				 }
			 }

            this.redrawBool=true;
			Invalidate();
		}

		/// <summary>
		/// Save game to file
		/// </summary>
		/// <param name="fileName">name of file to save</param>
		public  void SaveGameToFile( string fileName)
		{
			XmlTextWriter xtw = new XmlTextWriter(fileName,System.Text.UTF8Encoding.UTF8);

			xtw.WriteStartDocument();
			/*tw.WriteStartElement("mapConfiguration");

			// save map info
			//tw.WriteStartElement("mapInfo");

			//tw.WriteStartAttribute("mapType", string.Empty);
			//tw.WriteRaw(mapType.ToString());
			//tw.WriteEndAttribute();

			tw.WriteStartAttribute("cellNumber", string.Empty);
			tw.WriteRaw(mapData.CellDataArray.GetLength(0).ToString());
			tw.WriteEndAttribute();

			tw.WriteStartAttribute("currentTime", string.Empty);
			tw.WriteRaw(time.ToString());
			tw.WriteEndAttribute();

			tw.WriteEndElement();
			*/

			// save map data
			xtw.WriteStartElement("mapData");
			
			string checkedNbStr;
			//int cellsNumber = mapData.CellDataArray.GetLength(0);
			for (int row=0 ; row<Constants.nbOfCells ; row++)
			{
				for (int col=0 ; col<Constants.nbOfCells ; col++)
				{
					xtw.WriteStartElement("Data");

					// original nb attribute
					xtw.WriteStartAttribute("originalNumber", string.Empty);
					xtw.WriteRaw(cellGrid[row,col].cellData.originalNb.ToString());
					xtw.WriteEndAttribute();

					
					// read only attribute
					xtw.WriteStartAttribute("readonly", string.Empty);
					xtw.WriteRaw(cellGrid[row,col].cellData.readOnly.ToString());
					xtw.WriteEndAttribute();

					// selected number attribute
					xtw.WriteStartAttribute("problemNumber", string.Empty);
					xtw.WriteRaw(cellGrid[row,col].cellData.problemNb.ToString());
					xtw.WriteEndAttribute();

					// marked values attribute
					xtw.WriteStartAttribute("checkedNumbers", string.Empty);
					
					checkedNbStr= "";
					for (int k=0 ; k< cellGrid[row,col].CheckedNbs.Count; k++)
					{
						checkedNbStr+=((int)cellGrid[row,col].CheckedNbs[k]).ToString();
					}

					xtw.WriteRaw(checkedNbStr);
					
					//xtw.WriteEndAttribute();

					xtw.WriteEndElement();
				}
			}

			xtw.WriteEndElement();
			xtw.Close();
		}

		/// <summary>
		/// Load game data from file
		/// </summary>
		/// <param name="fileName">name of file to be loaded</param>
		public  void LoadGameFromFile(string fileName)
		{
			// check that file exists
			if(!File.Exists(fileName))
			{
				MessageBox.Show("The specified does not exist","Error"); 
				return;
			}
			
			try
			{
				this.currentlyFilledCells=0;
				System.Xml.XmlDocument xdoc = new XmlDocument();
				xdoc.Load(fileName);
				XmlElement root = xdoc.DocumentElement;
				System.Xml.XmlNodeList nodeList;
				//System.Xml.XmlNode node;
				
				//time = Convert.ToInt32(node.Attributes["currentTime"].Value);

				// load map data
			
				nodeList = root.ChildNodes;//.Item(0).ChildNodes;
				
				bool readOnly=false;;
				int onb=0,pnb=0;
				int k=0;
      			string checkedNbStr;
				
				for (int row=0 ; row<Constants.nbOfCells ; row++)
				{
					for (int col=0 ; col<Constants.nbOfCells ; col++)
					{
						readOnly=Convert.ToBoolean(nodeList.Item(k).Attributes["readonly"].Value);
						pnb= Convert.ToInt32(nodeList.Item(k).Attributes["problemNumber"].Value);
						onb=Convert.ToInt32(nodeList.Item(k).Attributes["originalNumber"].Value);

                        //check for invalid nb
						if((!Constants.validNbs.Contains(onb) && onb !=Constants.NoNumber)
							|| (!Constants.validNbs.Contains(pnb) && pnb !=Constants.NoNumber) )
                            throw new Exception();				
						
						cellGrid[row,col].cellData.readOnly =readOnly;
						cellGrid[row,col].cellData.problemNb=pnb;
						cellGrid[row,col].cellData.originalNb= onb;

						if(pnb!=Constants.NoNumber)
						{
							this.currentlyFilledCells++;
						}

						checkedNbStr = Convert.ToString(nodeList.Item(k).Attributes["checkedNumbers"].Value);
						
						cellGrid[row,col].CheckedNbs.Clear();
						for (int m=0 ; m<checkedNbStr.Length ;m++)
						{
							cellGrid[row,col].CheckedNbs.Add(Convert.ToInt32(checkedNbStr[m].ToString()));
						}
						k++;
					}
				}
				for(int row=0;row<Constants.nbOfCells;row++)
				{
					for(int col=0;col<Constants.nbOfCells;col++)
					{
						searchCandidates(row,col);
					}
				}
				redrawBool=true;
				Invalidate();
				this.Enabled=true;
			}
			catch(Exception ex)
			{
				MessageBox.Show("The File "+fileName+" is corrupt\n"+ex.Message,"Error");
				return;
			}
		}

		public void showNext()
		{
			for(int row=0;row<Constants.nbOfCells;row++)
			{
				for(int col=0;col<Constants.nbOfCells;col++)
				{
				   if(cellGrid[row,col].cellData.problemNb!=Constants.NoNumber)
				    	continue;
					int HB=(int)(row/Constants.nbOfLines);
					int VB=(int) (col/Constants.nbOfLines);
					if(cellGrid[row,col].cellData.candidateNb.Count==1)
					{						
						cellGrid[row,col].cellData.problemNb=(int)cellGrid[row,col].cellData.candidateNb[0];
						currentlyFilledCells++;
						autoUpdateMarkedCandidates(row,col);
						redrawBool=true;
						Invalidate();
						return;						
					}
						
					else   //find nb that cannot come into any cell of that block
					{
						for(int index=0;index<cellGrid[row,col].cellData.candidateNb.Count;index++)
						{
							int number=(int)cellGrid[row,col].cellData.candidateNb[index];
							bool found=false;
							for(int i=HB*Constants.nbOfLines;i<(HB+1)*Constants.nbOfLines;i++)                                
								for(int j=VB*Constants.nbOfLines;j<(VB+1)*Constants.nbOfLines;j++)                                    
									if(cellGrid[i,j].cellData.candidateNb.Contains(number) && !(i==row && j==col))
									{
										j=(VB+1)*Constants.nbOfLines; //exit from loop
										i=(HB+1)*Constants.nbOfLines;
										found =true;
									}                                   
							if(found)
								continue;
							else
							{
								//	MessageBox.Show("nb found");
								//the candidate  nb is found                                   
								cellGrid[row,col].cellData.problemNb=number;								
								//cellGrid[row,col].cellData.candidateNb.Clear();
								autoUpdateMarkedCandidates(row,col);
								currentlyFilledCells++;
								redrawBool=true;
								Invalidate();
								return;
							}
						}											
					}
					
				}
			}
		}
	}
}
